package com.itheima.article.service.impl;

import com.itheima.article.dto.ArticleHomeDto;
import com.itheima.article.dto.ArticleInfoDto;
import com.itheima.article.dto.ArticleVisitStreamMess;
import com.itheima.article.mapper.ApArticleConfigMapper;
import com.itheima.article.mapper.ApArticleContentMapper;
import com.itheima.article.pojo.ApArticle;
import com.itheima.article.mapper.ApArticleMapper;
import com.itheima.article.pojo.ApArticleConfig;
import com.itheima.article.pojo.ApArticleContent;
import com.itheima.article.service.ApArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.article.vo.ArticleInfoVo;
import com.itheima.article.vo.HotArticleVo;
import com.itheima.common.constants.BusinessConstants;
import com.itheima.common.enums.HttpCodeEnum;
import com.itheima.common.exception.LeadNewsException;
import com.itheima.common.vo.PageResultVo;
import com.itheima.common.vo.ResultVo;
import com.itheima.wemedia.feign.WmNewsFeign;
import com.itheima.wemedia.pojo.WmNews;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @version 1.0
 * @description <p>已发布的文章信息 业务实现</p>
 * @package com.itheima.article.service.impl
 */
@Service
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Resource
    private ApArticleContentMapper apArticleContentMapper;

    @Resource
    private ApArticleConfigMapper apArticleConfigMapper;

    @Resource
    private WmNewsFeign wmNewsFeign;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 同步时，保存文章数据
     *
     * @param dto
     */
    @Override
    @Transactional
    public void saveArticleInfo(ArticleInfoDto dto) {
        //1. 保存文章表数据
        ApArticle apArticle = dto.getApArticle();
        // 判断是否是修改的
        Long oldArticleId = apArticle.getId();
        if(oldArticleId != null) {
            // 修改
            // 标记旧文章为删除
            deleteOldArticle(oldArticleId);
            // 添加新记录
            apArticle.setId(null);
        }
        save(apArticle);
        //2. 获取新增的文章id
        Long newArticleId = apArticle.getId();
        //3. 保存文章内容表数据
        ApArticleContent apArticleContent = dto.getApArticleContent();
        // 设置新增的文章id
        apArticleContent.setId(newArticleId);
        apArticleContent.setArticleId(newArticleId);// 这个字段是多余
        apArticleContentMapper.insert(apArticleContent);

        //4. 保存文章配置表数据
        ApArticleConfig apArticleConfig = dto.getApArticleConfig();
        apArticleConfig.setId(newArticleId);
        apArticleConfig.setArticleId(newArticleId);// 这个字段是多余
        apArticleConfigMapper.insert(apArticleConfig);
        //5. 远程调用自媒体更新新闻表的状态及articleId
        WmNews updatePojo = new WmNews();
        updatePojo.setId(dto.getWmNewsId());
        // 已发布
        updatePojo.setStatus(BusinessConstants.WmNewsConstants.STATUS_PUBLISHED);
        // 发布库中的文章id
        updatePojo.setArticleId(newArticleId);
        ResultVo updateResult = wmNewsFeign.updateWmNews(updatePojo);
        //6. 解析远程调用结果
        if (!updateResult.isSuccess()) {
            log.error("远程调用自媒体更新文章状态失败!{}，{}", dto.getWmNewsId(), updateResult.getErrorMessage());
            throw new LeadNewsException("远程调用自媒体更新文章状态失败! " + updateResult.getErrorMessage());
        }
    }

    /**
     * App文章列表分页查询
     *
     * @param dto
     * @param type
     * @return
     */
    @Override
    public List<ApArticle> findPage(ArticleHomeDto dto, int type) {
        List<ApArticle> list = getBaseMapper().findPage(dto, type);
        return list;
    }

    /**
     * 加载文章详情
     *
     * @param articleId
     * @return
     */
    @Override
    public ArticleInfoVo loadArticleInfo(Long articleId) {
        // 先查询配置表ap_article_config
        ApArticleConfig apArticleConfig = apArticleConfigMapper.selectById(articleId);
        if(null == apArticleConfig){
            throw new LeadNewsException(HttpCodeEnum.DATA_NOT_EXIST);
        }
        // 判断是否为下架或删除，如果是则报错
        if(apArticleConfig.getIsDown() == 1 || apArticleConfig.getIsDelete() == 1){
            throw new LeadNewsException("文章已下架或删除!");
        }
        // 再查询内容表ap_article_content
        ApArticleContent apArticleContent = apArticleContentMapper.selectById(articleId);
        ArticleInfoVo vo = new ArticleInfoVo();
        vo.setConfig(apArticleConfig);
        vo.setContent(apArticleContent);
        return vo;
    }

    /**
     * 计算5天前的文章的分数，并存入redis 使用zset，热点文章排序
     */
    @Override
    public void computeAndSave2Redis() {
        //1. 查询5天前发布的文章
        List<ApArticle> last5DaysArticles = list5DaysBefore();
        //2. 计算每篇文章的分数值
        List<HotArticleVo> voList = last5DaysArticles.stream()
            .map(apArticle -> HotArticleVo.build(apArticle, computeScore(apArticle))).collect(Collectors.toList());

        //3. 按频道分组，
        Map<Integer, List<HotArticleVo>> channelMap = voList.stream().collect(Collectors.groupingBy(HotArticleVo::getChannelId));

        // 所有文章的频道
        Set<Integer> channelIds = channelMap.keySet();
        // 遍历
        for (Integer channelId : channelIds) {
            // 每个频道的文章列表
            List<HotArticleVo> hotArticleVoList = channelMap.get(channelId);
            // 取分数最高的前30篇文章
            List<HotArticleVo> top30List = hotArticleVoList.stream()
                // 排序
                .sorted(
                    // 升序
                    Comparator.comparing(HotArticleVo::getScore)
                        // 反转，升序反过来为降序
                        .reversed())
                // 取前30个
                .limit(30).collect(Collectors.toList());
            // 存入redis
            save2Redis(channelId,top30List);
        }

        //4. 如果是推荐，则取所有文章中分数最高的前30篇，存入redis
        List<HotArticleVo> recommendList = voList.stream().sorted(Comparator.comparing(HotArticleVo::getScore)
            .reversed()).limit(30).collect(Collectors.toList());
        // 存入redis
        save2Redis(BusinessConstants.ArticleConstants.DEFAULT_TAG,recommendList);
    }

    /**
     * 更新文章数量与重新计算再存入redis
     *
     * @param mess
     */
    @Override
    public void computeAndSave2RedisSingle(ArticleVisitStreamMess mess) {
        //1. 更新文章里的数量
        getBaseMapper().updateNumber(mess);
        //2. 给文章重新算分 * 3
        ApArticle apArticle = getById(mess.getArticleId());
        Integer score = computeScore(apArticle) * 3;
        //3. 存入redis
        //4. 频道的
        Integer channelId = apArticle.getChannelId();
        String channelKey = BusinessConstants.ArticleConstants.HOT_ARTICLE_FIRST_PAGE + channelId;
        stringRedisTemplate.opsForZSet().add(channelKey, mess.getArticleId().toString(), score.doubleValue());
        //   推荐的
        String recommendKey = BusinessConstants.ArticleConstants.HOT_ARTICLE_FIRST_PAGE + BusinessConstants.ArticleConstants.DEFAULT_TAG;
        stringRedisTemplate.opsForZSet().add(recommendKey, mess.getArticleId().toString(), score.doubleValue());
    }

    /**
     * 把热点文章存入redis
     * @param key
     * @param recommendList
     */
    private void save2Redis(Serializable key, List<HotArticleVo> recommendList) {
        String redisKey = BusinessConstants.ArticleConstants.HOT_ARTICLE_FIRST_PAGE + key;
        for (HotArticleVo vo : recommendList) {
            // 使用zset存入文章与分数
            stringRedisTemplate.opsForZSet().add(redisKey, vo.getId().toString(), vo.getScore());
        }
    }

    /**
     * 查询5天前发布的文章
     * @return
     */
    private List<ApArticle> list5DaysBefore() {
        // 条件 5天前
        String last5Days = LocalDateTime.now().minusDays(5).toLocalDate().toString();
        // 查询
        List<ApArticle> list = getBaseMapper().selectAfterDate(last5Days);
        // 返回结果
        return list;
    }

    /**
     * 计算文章具体分值
     *
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;

        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * BusinessConstants.ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }

        if (apArticle.getViews() != null) {
            score += apArticle.getViews();
        }

        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * BusinessConstants.ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }

        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * BusinessConstants.ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }

    /**
     * 标记旧文章为删除
     * @param oldArticleId
     */
    private void deleteOldArticle(Long oldArticleId) {
        ApArticleConfig updatePojo = new ApArticleConfig();
        // 设置更新的字段值
        updatePojo.setIsDelete(1);// 代表已删除
        updatePojo.setId(oldArticleId);
        apArticleConfigMapper.updateById(updatePojo);
    }
}
